#####################################################################
# Product:      KnowledgeEngine (Plone Edition)
#
# File:         KERLProcessor.py
#
# Copyright:    Copyright (c) 2005, Faulkner Technologies
#
# Author:       Brian Oliver
#
# Description:  This file defines functions for evaluating and
#               translating KERL expressions, including evaluating
#               KERL expressions "on the server-side" and translating
#               KERL expressions into JavaScript, for implementing
#               showif.
#
#               Included in this file are functions for manipulating
#               and optimizing the representation of KERL expressions.
#
# See Also:     Component, KERLExtensions
#####################################################################

#####################################################################
## KnowledgeEngine Library Imports
from Common import *
from Caching import RequestCache

#####################################################################
## Zope Library Imports
from DateTime import DateTime
 
#####################################################################
## Python Library Imports
import types

#####################################################################
# Exceptions

class KERLException(Exception): pass

class ComponentNotFound(KERLException): pass
class ComponentDisabled(ComponentNotFound): pass
    
class ComponentTypeException(KERLException): pass
class InvalidKerlExpression(KERLException): pass
class InvalidKerlOperator(InvalidKerlExpression): pass
class InvalidKerlReference(InvalidKerlExpression): pass    


#####################################################################

import re
r_kerlwhitespace = re.compile("[\r\n\t]")

def parseKERL(KERLstring):
    """
        Parses and returns an internal representation of the KERL string        
    """
    
    # Strip newlines and other invalid whitespace from the KERL string to allow 
    # better formatting of large expressions in editor
    KERLstring = r_kerlwhitespace.sub( " ", KERLstring )

    if KERLstring == "":
        return None
    else:
        # Evaluate the string, passing an empty namespace (for security)
        return eval(KERLstring, {}, {})
    
#####################################################################    
    
def validateKERL(expression, component ):
        """ Validates a KERL Statement """
        import traceback
        try:
            kerl = parseKERL( expression )
        except Exception, e:
            traceback.print_exc()
            exceptionName = str(e.__class__).split(".")[-1]
            return "%s: %s" % (exceptionName, str(e))
                    
        
        
        model = component.getModel()                    
                    
        try:
            resolvedKerl = resolveDynamicReferences(kerl, component, assessment=None)
            js = compileKERLtoJavaScript( resolvedKerl, model )                   
        except KERLException, e:
            exceptionName = str(e.__class__).split(".")[-1]
            
            traceback.print_exc()
            return "%s: %s" % (exceptionName, str(e))
                    
        return None                    
    
#####################################################################

def isComponentOperator(operator):
    """
        Returns if the kerl expression is a component operator
    """
    
    return not operator in ['any', 'all', 'none', 'assessment', 'currentuser','component']
    
#####################################################################

def compileKERLtoJavaScript(expression, model, component=None):
    """
        Returns a Python string that represents a JavaScript
        expression, which may be used to evaluate the status
        of the KERL expression (in a web browser)

        NOTE: this method is primarily used to produce
        JavaScript expressions that form the basis of the
        client-side showif technology.

        PARAM: expression - a KERL expression in parsed format (see parseKERL)
        PARAM: model - a Model instance to scope the evaluation of the expression

        RETURNS: "" if there is no expression

        RETURN TYPE: String         
    """

    #is there an expression to evaluate?
    if expression == 'always':
        return 'true'
        
    elif expression == 'never':
        return 'false'
        
    elif expression != None:
        
        #print "Eval: %s" % `expression`
        
        #get the base operator
        opr = expression[0]

        #is the operator a built in KERL operator? (all, any, none)
        if opr == "all":
            bopr = " & "
            iopr = ""
            popr = ""

        elif opr == "any":
            bopr = " | "
            iopr = ""
            popr = ""

        elif opr == "none":
            bopr = " & "
            iopr = "!("
            popr = ")"

        elif opr == "assessment":

            #TODO: one day we should make some of the 'assessment' operator work on the client-side!
            return ""

        elif opr == "currentuser":

            #TODO: one day we should make some of the 'currentuser' operator work on the client-side!
            return ""

        elif opr == "component":

            if not component:
                raise ComponentNotFound, "No reference to 'component' in this context"
            
            return component.compileKERLtoJavaScript( expression )

        else:
            #assume it is a component specific operator
            #(opr holds the component identity)
            component = model.getComponent(opr)
            
            if not component:
                raise ComponentNotFound(opr)
            
            if component.getIsDisabled():
                raise ComponentDisabled(opr) 
            
            
            
            #have the component return the javascript for the expression
            return component.compileKERLtoJavaScript(expression)

        #build the javascript expression (recursively)
        result = ""

        #enumerate over each of the sub expressions, converting them to javascript
        #as necessary
        for subexpression in expression[1:]:
        
            #add the binary operator if necessary
            if result != "":
                result = result + bopr
            
            #is the operator of the subexpression a KERL built one?
            if subexpression[0] == "all" or subexpression[0] == "any" or subexpression[0] == "none":
                result = result + iopr + "(" + compileKERLtoJavaScript(subexpression, model) + ")" + popr
            else:
                #the expression is a component extension.. so get the component
                component = model.getComponent(subexpression[0])             

                if not component:
                    raise ComponentNotFound(subexpression[0])
                    #print "Could not get component from expression %s" % subexpression
                    continue

                if component.getIsDisabled():
                    raise ComponentDisabled(opr) 

                #have the component return the javascript for the expression
                result = result + iopr + component.compileKERLtoJavaScript(subexpression) + popr

        return result
    
    else:
        return ""

#####################################################################

def resolveDynamicReferences(expression, component, assessment):
    """
        Resolve 'reference' clauses in the specified expression
        for the specified component in the provided assessment
    """
    
    if expression is None or len(expression) == 0:
        return None
    else:
        #process each element of the expression, resolving the dynamic references as necessary
        result = []
        
        for element in expression:

            #is the element a dynamic reference?            
            if type(element) is types.ListType and element[0] == "reference":

                #determine the kind of reference
                reference = element[1]
                
                if reference == "previous":
                    #get the type of component we want to locate
                    instanceType = element[2]

                    #locate the desired component from the specified component
                    referencedComponent = component.getPreviousComponentByInstanceOf(instanceType, assessment)

                    #did we locate it?                
                    if referencedComponent is not None:
                        #we found the referenced component so we must return it's identity
                        result = result + [referencedComponent.getIdentity()]
                    else:
                        #TODO: We couldn't find the required component... we should throw an exception
                        #result = result + [element]
                        raise InvalidKerlReference, element

            #the element is some nested list            
            elif type(element) is types.ListType:
                result = result + [resolveDynamicReferences(element, component, assessment)]
            
            #the element is atomic
            else:
                result = result + [element]
        
        return result
    
#####################################################################

class KERLOperators:
    
    Equal    = ('=','==','eq','equals')
   

def evalExpression( expression, assessment, component=None ):
    """ Evaluates an expression on the right hand side of an operator """
    
    if expression[0] == 'component':
        if not component:
            raise ComponentNotFound, "No component in this context" 
        
        return assessment.getComponentByIdValue( component.getIdentity() )
    
    elif expression[0].startswith('idx:'):
        return assessment.getSearchIndexValue( expression[0][4:] )
    
    else: # Assume component operator
        print "evalExpression: getComponentByIdentityValue - %s" % expression[0]
        v = assessment.getComponentByIdentityValue( expression[0] )        
        print "Got: " + `v`
        return v
                
                
               

def evaluate(expression, assessment, component=None):
    """
        Evaluate the specified expression using the specified assessment,
        (on the server-side) returning true or false if the expression holds.
    """
    
    print expression

     #is there an expression to evaluate?
    if expression != None:
        
        #get the base operator
        opr = expression[0]

        if len(expression) >= 3 and type(expression[2]) is tuple:
            # HACK - Allow some expressions to be evaluated on the right hand site of an operator if wrapped in a tuple
            expression[2] = evalExpression( expression[2], assessment, component ) 

        #is the operator a built in KERL operator? (all, any, none)
        if opr == "all":
            bopr = " and "
            iopr = ""
            popr = ""

        elif opr == "any":
            bopr = " or "
            iopr = ""
            popr = ""

        elif opr == "none":
            bopr = " and "
            iopr = "!("
            popr = ")"
            
        elif opr == "model":
            
            if expression[1] in KERLOperators.Equal:
                return assessment.getModel().getIdentity == expression[2]
            else:
                raise InvalidKerlOperator, expression[1]
            

        elif opr == "assessment":            
            #print "*** " + `groupIdentities`
            #createdby operator   
            
            
            
            if len(expression) == 3 and expression[1] == 'createdby':         
                return assessment.getCreatedBy() == expression[2]
            
            elif len(expression) == 3 and expression[1] == 'responsible':         
                return assessment.getResponsible() == expression[2]            

            elif len(expression) == 3 and expression[1] == 'createdbygroup':
                checkGroup = expression[2]
                userIdentity = assessment.getCreatedBy()
                
                groupProviderService = assessment.getModel().getGroupProviderService()                
                groupIdentities = [group.getIdentity() for group in groupProviderService.getGroupsForActor(userIdentity)]
                
                print "*** " + `groupIdentities`
                
                return (checkGroup in groupIdentities)

            elif len(expression) == 2 and expression[1] == 'ischeckedout':         
                return assessment.isCheckedOut()
            
            elif len(expression) == 2 and expression[1] == 'isnotcheckedout':         
                return not assessment.isCheckedOut()            

            #islocked operator   
            elif len(expression) == 2 and expression[1] == 'islocked':         
                return assessment.isLocked()
            
            elif len(expression) == 2 and expression[1] == 'isnotlocked':
                return not assessment.isLocked()

            #date operators
            elif len(expression) == 3 and expression[1] in ['createdbefore','createdafter','modifiedbefore','modifiedafter']:
                if expression[1] == 'createdbefore':
                    return assessment.getInstantCreated() < DateTime(expression[2])
                elif expression[1] == 'createdafter':
                    return assessment.getInstantCreated() >= DateTime(expression[2])
                if expression[1] == 'modifiedbefore':
                    return assessment.getInstantModified() < DateTime(expression[2])
                elif expression[1] == 'modifiedafter':
                    return assessment.getInstantModified() >= DateTime(expression[2])
                else:
                    #TODO: there is some problem with the expression.  we should throw an exception
                    return false
                
            #repository operators
            else:
                #get the repositories in which the assessment resides
                assessmentStorageService = assessment.getModel().getAssessmentStorageService()
                repositoryIdentities = assessmentStorageService.getRepositoriesForAssessment(assessment)

                if len(expression) == 3 and expression[1] == 'inrepository':
                    return expression[2] in repositoryIdentities

                elif len(expression) == 3 and expression[1] == 'notinrepository':
                    return not expression[2] in repositoryIdentities

                elif len(expression) == 3 and expression[1] == 'onlyinrepository':                
                    return len(repositoryIdentities) == 1 and expression[2] in repositoryIdentities

                elif len(expression) == 2 and expression[1] == 'onlyincurrentuserrepository':         
                    return len(repositoryIdentities) == 1 and assessment.getOpenedBy() in repositoryIdentities

                elif len(expression) == 2 and expression[1] == 'isorphaned':         
                    return len(repositoryIdentities) == 0

                else:
                    #TODO: there is some problem with the expression.  we should throw an exception
                    return false

        elif opr == "currentuser":
          
            userIdentity = assessment.getModel().portal_membership.getAuthenticatedMember().getId()
            
            #print "USER IDENTITY: " + `userIdentity`
        
            #do we know the user that opened the assesssment?
            if userIdentity is not None:

                if expression[1] == "responsible":
                    print "%s == %s" % ( userIdentity, assessment.getResponsible() )
                    return userIdentity == assessment.getResponsible()

                #are dealing with groups operator?
                elif len(expression) == 3 and (expression[1] == 'ingroup' or expression[1] == 'notingroup'):

                    #get the group provider service so we can look up the groups in which as user belongs
                    groupProviderService = assessment.getModel().getGroupProviderService()                
                    groupIdentities = [group.getIdentity() for group in groupProviderService.getGroupsForActor(userIdentity)]

                    print "%s - %s (%s)" % ( expression[1], expression[2], groupIdentities )
                    
                    if expression[1] == 'ingroup':
                        return expression[2] in groupIdentities
                    elif expression[1] == 'notingroup':
                        return not expression[2] in groupIdentities
                    else:
                        #TODO: unknown currentuser operator
                        return false        

                #are dealing with a 'is' user operator?
                elif len(expression) == 3 and expression[1] == 'is':
                    return expression[2] == userIdentity

                #are dealing with a 'isnot' user operator?
                elif len(expression) == 3 and expression[1] == 'isnot':
                    return expression[2] != userIdentity

                else:
                    #TODO: unknown use of the 'currentuser' operator
                    return false
            else:
                #the current user is unknown (perhaps this should return true?)
                return false
                
        elif opr == "component":
            if not component:
                raise ComponentNotFound, "No reference to 'component' in this context"
                
            return component.evaluate( expression, assessment )
                
        else:
            #assume it is a component specific operator
            #(opr holds the component identity)
            component = assessment.getModel().getComponent(opr)
            
            if not component:
                raise ComponentNotFound, "Could not find component %s" % `opr` 
            
            if expression[1] == 'firstattempt':
                assessmentStorageService = assessment.getModel().getAssessmentStorageService()
                state = assessmentStorageService.loadComponentState( assessment, component.getIdentity() )
                
                if state == None: # No component state saved, therefore this is the first attempt
                    return "always"
                else:
                    return "never"
                    
            #print "Expression: %s" % `expression`                    
                    
            #have the component return the evaluation of the expression
            return component.evaluate(expression, assessment)

        #build a python expression (recursively)
        result = ""

        #enumerate over each of the sub expressions, converting them to a python expression as necessary
        for subexpression in expression[1:]:
            
            #add the binary operator if necessary
            if result != "":
                result = result + bopr

            #is the operator of the subexpression a KERL built one?
            if not isComponentOperator(subexpression[0]):
                result = result + iopr + "(" + repr(evaluate(subexpression, assessment)) + ")" + popr
            else:
                #the expression is a component extension.. so get the component
                component = assessment.getModel().getComponent(subexpression[0])

                #have the component return the javascript for the expression
                result = result + iopr + repr(component.evaluate(subexpression, assessment)) + popr

        return eval(result)

    else:
        return true
    
#####################################################################

def getDependencies(expression, model):
    """
        Returns a dictionary of components that are required to
        evaluate the provided KERL expression with in the scope of
        the specified model.
    """

    #is there an expression?
    if expression is not None:
        
        #get the base operator
        operator = expression[0]

        #the 'reference' operator?
        if type(operator) is types.ListType and operator[0] == "reference": 
            #there are no direct dependancies for the 'reference' operator
            return {}

        #a combinator operator?
        elif operator in ["all", "any", "none"]:
            #start a resulting dictionary
            result = {}

            #adde the dependencies for the sub expressions to the result
            for subexpression in expression[1:]:            
                result.update(getDependencies(subexpression, model))

            return result

        #the assessment operator
        elif operator == "assessment":

            #repository assessment operators have dependencies
            if len(expression) == 3 and expression[1] in ['inrepository', 'notinrepository', 'onlyinrepository']:
                repositoryIdentity = expression[2]
                repository = model.getComponent(repositoryIdentity)
                return {repository.getIdentity():repository}

            else:
                #no other 'assessment' operator have dependencies
                return {}

        #the currentuser operator
        elif operator == "currentuser":
            #there are no component dependencies for the currentuser operator
            return {}
           
        #a component operator
        else:
            #assume it is a component specific operator
            #(operator holds the component identity)
            component = model.getComponent(operator)
            
            #was the component found? (if not, we have an invalid identity)
            if component is not None:
                #have the component return the dependencies for the expression
                return component.getDependencies(expression)
                
            else:
                #TODO: we should probably throw an exception here as the component identity is unknown!           
                return {}
        
    else:
        return {}
    
#####################################################################
def getComponentShowIfTransitiveClosure(component, model, assessment = None, page=None):
    """
        Returns a KERL expression representing the
        showif transitive closure for the specified component.
    """        


# A "transitive closure" is an expression containing all of the conditions, 
# both explict and implicit (via transitivity), that must be true for
# a component to be shown.
#
# The following defines the conditions underwhich a component is shown.
#
# A component is shown if and only if it's parents are shown and
# the showif expression for the component is either;
#     a). empty, or 
#     b). evaluates to true AND 
#         the components used within the showif expression
#         used to define the showif are also shown.  
#
#    Notice this definition is recursive as;
#     a). the parents must also be shown, and
#     b). each component in a showif expression must also be shown.
   
    
    cache = RequestCache('getComponentShowIfTransitiveClosure', model.REQUEST)
    
    result = []

    #----------------------
    #RULE: A component is only shown iff it's parents are shown
    #and the showif for the said component permits the component
    #to be shown.
  
    chash = None
  
    if not component:
        raise ComponentNotFound, "Component not found in KERL Expression"

    #only Components can do this
    if component.isInstanceOf("Component"):
        #the parents of the component effect the component
        effectingComponents = component.getParents()

        #add the component itself to the components possibily 
        #having an impact on the display of the component
        effectingComponents = [component] + effectingComponents

        #add conditions underwhich the effecting components are shown
        for effectingComponent in effectingComponents:
            # get the transitive closure for the effectingComponent parent showif 
            # (with resolved dynamic references) and
            #add it to the result        


            # Generate a hash to use for the cache
            chash = component.getIdentity() + "-" + effectingComponent.getIdentity()
                        
            if page:
                chash += "-" + page.getIdentity()
                         
            if chash in cache: 
                #print "Cache Hit"
                expression = cache[chash]                
                return expression
            else:
                #print "Cache Miss"
                expression = getRuleShowIfTransitiveClosure(resolveDynamicReferences(effectingComponent.getShowIfKERL(), effectingComponent, assessment), model, assessment, page=page)            
            

            
                #was a valid expression returned?
                if expression != None:                   
                    if page:
                        #print "PAGE:   " + `page`
                        #print "BEFORE: " + `expression`
                        expression = optimizeKERLForPageWithAssessment(expression, assessment, page, component=component)
                        #print "AFTER:  " + `expression`
                        
                        
                    if expression and expression != 'always':
                        result = result + [expression]
                        
                
    #----------------------            
    #add an "all" if there is more than one condition in the result
    if len(result) > 1:
        result = ['all'] + result

    elif len(result) == 1:
        #as there is only one result, use it without an operator
        result = result[0]

    elif len(result) == 0:
        result = None
            
    
    if chash: cache[chash] = result
    
        
    
    

    return result

#####################################################################

def getRuleShowIfTransitiveClosure(expression, model, assessment = None, page=None):
    """
        Returns a KERL expression representing the showif transitive
        closure for the specified expression.
    """

#params:  expression - a KERL expression in it's native python representation
#returns: a KERL expression in it's native python representation

# A "transitive closure" is an expression containing all of the conditions, 
# both explict and implicit (via transitivity), that must be true for
# a component to be shown.

# NOTE: this method is different from getComponentShowIfTransitiveClosure as it
#       expands the provided rule to create a transitive closure, where as
#       getComponentShowIfTransitiveClosure creates a transitive closure for
#       the specified component


    #do we have a KERL expression?
    if expression != None:

        #get the KERL operator
        opr = expression[0]

        if opr == 'assessment':
            #no transitive closure for the assessment operator
            result = expression

        elif opr == 'currentuser':
            #no transitive closure for the currentuser operator
            result = expression

        elif opr == 'never':
            result = 'never'
            
        #is the expression a KERL operator or a component extension
        elif isComponentOperator(opr):
            #we're processing a component extension
            
            #get the component
            component = model.getComponent(opr)

            if not component:
                raise ComponentNotFound, opr

            #determine the transitive closure for the component
            ctc = getComponentShowIfTransitiveClosure(component, model, assessment, page=page)

            #does the component have a transitive closure
            if ctc != None:                
               #the result is the transitive closure of the component AND the specified rule
                result = ['all', ctc, expression]                
            else:
                #no transitive closure, so the result is just the specified rule 
                result = expression

        else:
            #the expression uses a KERL built-in operator (containing an 'all', 'any' or 'none' operator)
            
            #get the operator (it will stay the same)
            result = [opr]

            #find the transitive closure for each of the sub expressions in the expression
            for subexpression in expression[1:]:
                
                #get the transitive closure for the current subexpression
                rtc = getRuleShowIfTransitiveClosure(subexpression, model, assessment, page=page)

                #was there a valid transitive closure?
                if rtc != None:
                    result = result + [rtc]              

        #any result? if not return None           
        if result == []:
            return None
        else:
            return result

    else:
        #if no rule is provided, then there is no transitive closure
        return None

#####################################################################

def updateKERLIdentities(expression, identityMappings, indexedContainer):
    """
        Updates component identity references (strings) in a KERL
        expression with those specified in the identityMappings
        dictionary (old -> new identities).
        
        Returns a new KERL containing updated identities.
    """
    
    ###print "DEBUG: KERLProcessor.updateKERLIdentities: Commenced"
    
    #is there an expression?
    if expression != None:
        
        #get the base operator
        opr = expression[0]

        #is the operator a built in KERL operator? (all, any, none)
        if opr in ["all", "any", "none"]:

            #the operator is the same for the result
            result = [opr]

            #enumerate over each of the sub expressions, and update their identities
            for subexpression in expression[1:]:
                    
                #update the subexpression (recursive)
                result += [updateKERLIdentities(subexpression, identityMappings, indexedContainer)]
                    
            #return the result
            return result

        elif opr == "assessment":
            #nothing to update in an assessment operator
            return expression

        elif opr == "currentuser":
            #nothing to update in an currentuser operator
            return expression

        #a dynamic reference operator
        elif type(opr) is types.ListType and opr[0] == "reference":
            
            #nothing to update for the dynamic reference operator
            return expression

        #a component operator        
        else:
            
            #does the component have a new identity?
            if identityMappings.has_key(opr):
                #get the new component identity
                componentIdentity = identityMappings[opr]
            else:
                #old component.. no new identity 
                #(we still have to resolve mappings as the component rule 
                # may refer to other components)
                componentIdentity = opr

            #get the component
            print "Looking for %s (was %s) in %s" % (componentIdentity, opr, indexedContainer.absolute_url())
            component = indexedContainer.getComponent(componentIdentity)

            #was the component found? (if not, we have an invalid identity)
            if component:

                #let the component resolve it's own kerl extension
                return component.updateKERLIdentities(expression, identityMappings)

            else:
                #raise "!!! Could not find component %s while updating KERL" % componentIdentity
                raise ComponentNotFound, "Could not find component %s while updating KERL" % `componentIdentity`
                
                #return expression # Return the original expression for the user to manually fix.
                
                #TODO: we should probably throw an exception here as the component identity is unknown!   
                #return None
        
    else:
        return None    
    
#####################################################################

def optimizeKERLForPageWithAssessment(expression, assessment, page, component=None):
    """
        Return an optimized showif kerl for the specified assessment (cache) on
        the specified page.
        
        This method effectively does partial evaluation of kerls for
        components that are NOT on the specified page, by retrieving
        the state of the said components from the provided assessment.
        
        Given the assessment state, there are three possible outcomes.
        1). the expression will always evaluate to true (always shown)
        2). the expression will always evaluate to false (never shown)
        3). some server-side evaluation will still need to take place
        
        RETURNS: 
        'always' - if the kerl will always evaluate to true (always shown)
        'never' - if the kerl will always evaluate to false (not shown)
        a kerl - an optimized kerl otherwise
    """
                
    if expression and len(expression) >= 3 and type(expression[2]) is tuple:
        # HACK - Allow some expressions to be evaluated on the right hand site of an operator if wrapped in a tuple
        expression[2] = evalExpression( expression[2], assessment=assessment, component=component )                 
        
    cache = RequestCache('optimizeKERLForPageWithAssessment', page.REQUEST)
    c_hash = `expression` + assessment.getIdentity() + `page.getIdentity()`
   
   
    if c_hash in cache:
        return cache[c_hash]
        

    if expression is None or len(expression) == 0:
        result = 'always'
    elif expression in ('always','never'):
        return expression
    else:
        #get the operator from the expression
        opr = expression[0]

        #optimize the 'assessment' operator
        if opr == 'assessment':            
            #get the repositories in which the assessment resides
            assessmentStorageService = assessment.getModel().getAssessmentStorageService()
            repositoryIdentities = assessmentStorageService.getRepositoriesForAssessment(assessment)

            if expression[1] == "responsible":                
                current_user_id = context.portal_membership.getAuthenticatedMember()                
                result = iif( current_user_id == assessment.getResponsible(), "always", "never" )
                

            elif len(expression) == 3 and expression[1] == 'inrepository':
                result = iif(expression[2] in repositoryIdentities, 'always', 'never')

            elif len(expression) == 3 and expression[1] == 'notinrepository':
                result = iif(not expression[2] in repositoryIdentities, 'always', 'never')

            elif len(expression) == 3 and expression[1] == 'onlyinrepository':                
                result = iif(len(repositoryIdentities) == 1 and expression[2] in repositoryIdentities, 'always', 'never')
                
            elif len(expression) == 2 and expression[1] == 'onlyincurrentuserrepository':         
                return iif(len(repositoryIdentities) == 1 and assessment.getOpenedBy() in repositoryIdentities, 'always', 'never')

            #date operators
            elif len(expression) == 3 and expression[1] in ['createdbefore','createdafter','modifiedbefore','modifiedafter']:
                if expression[1] == 'createdbefore':
                    return iif(assessment.getInstantCreated() < DateTime(expression[2]), 'always', 'never') 
                elif expression[1] == 'createdafter':
                    return iif(assessment.getInstantCreated() >= DateTime(expression[2]), 'always', 'never') 
                if expression[1] == 'modifiedbefore':
                    return iif(assessment.getInstantModified() < DateTime(expression[2]), 'always', 'never') 
                elif expression[1] == 'modifiedafter':
                    return iif(assessment.getInstantModified() >= DateTime(expression[2]), 'always', 'never') 
                else:
                    #TODO: there is some problem with the expression.  we should throw an exception
                    result = 'never'
                
            else:
                #TODO: there is some problem with the expression.  we should throw an exception
                result = 'never'

        #optimize the 'currentuser' operator
        elif opr == "currentuser":
            #get the userIdentity that opened the assessment
            #userIdentity = assessment.getOpenedBy()
            userIdentity = context.portal_membership.getAuthenticatedMember()
            
            
            #do we know the user that opened the assesssment?
            if userIdentity is not None:

                if expression[1] == "responsible":
                    result = iif( assessment.getResponsible() == userIdentity, "always", "never" )

                #are dealing with groups operator?
                elif len(expression) == 3 and (expression[1] == 'ingroup' or expression[1] == 'notingroup'):

                    

                    #get the group provider service so we can look up the groups in which as user belongs
                    groupProviderService = assessment.getModel().getGroupProviderService()                
                    groupIdentities = [group.getIdentity() for group in groupProviderService.getGroupsForActor(userIdentity)]

                    print "%s - %s (%s)" % ( expression[1], expression[2], groupIdentities )

                    g = groupProviderService.getGroupsForActor(userIdentity)
                           
                    if expression[1] == 'ingroup':
                        
                        #raise "%s in %s" % ( expression[2], groupIdentities )
                        
                        result = iif(expression[2] in groupIdentities, 'always', 'never')
                    elif expression[1] == 'notingroup':
                        result = iif(not expression[2] in groupIdentities, 'always', 'never')
                    else:
                        #TODO: unknown currentuser operator
                        result = 'never'        

                #are dealing with a 'is' user operator?
                elif len(expression) == 3 and expression[1] == 'is':
                    result = iif(expression[2] == userIdentity, 'always', 'never')

                #are dealing with a 'isnot' user operator?
                elif len(expression) == 3 and expression[1] == 'isnot':
                    result = iif(expression[2] != userIdentity, 'always', 'never')

                else:
                    #TODO: unknown use of the 'currentuser' operator
                    result = 'never'
            else:
                #the current user is unknown (perhaps this should return always?)
                result = 'never'

        elif opr == 'XXXXXcomponent':
            
            # Operate on 'ourself'.
            
            if not component: # Passed through as a kwarg
                raise ComponentNotFound, "No reference to 'component' in this context"
            
            
            else:                
                # No further optimizations available for 'component' operator
                result = expression
        
        elif opr == 'component' or isComponentOperator(opr):
            #locate the component using the assessment model
            if opr == 'component':            
                mycomponent = component
            else:
                mycomponent = assessment.getModel().getComponent(opr)
            
            if not mycomponent:
                raise ComponentNotFound, "Could not find component %s in expression %s" % (opr, expression)
            
            if expression[1] == 'firstattempt':
                assessmentStorageService = assessment.getModel().getAssessmentStorageService()
                state = assessmentStorageService.loadComponentState( assessment, component.getIdentity() )
                
                if state == None: # No component state saved, therefore this is the first attempt
                    result = "always"
                else:
                    result = "never"    
            elif mycomponent.isDescendant(page):
                #TODO:    return component.optimizeShowIfForPageWithAssessment(assessment, page)
                result = expression
            else:
                if evaluate(expression, assessment):
                    result = 'always'
                else:
                    result = 'never'
                
        else:
            #optimize the subexpressions
            subexpressions = [optimizeKERLForPageWithAssessment(subexpr, assessment, page, component=component) for subexpr in expression[1:]]

            #optimize the 'any' operator
            if opr == 'any':
                if 'always' in subexpressions:
                    result = 'always'                           #at least one is true!

                else:                
                    #remove 'never' values as they have no effect
                    subexpressions = [subexpr for subexpr in subexpressions if subexpr != 'never']

                    if len(subexpressions) == 0:
                        result = 'never'                        #no 'always' values means this expression fails
                    elif len(subexpressions) == 1:
                        result = subexpressions[0]              #as there is only 1 operator, the 'any' isn't needed
                    else:
                        result = [opr] + subexpressions 
            
            #optimize the 'all' operator
            elif opr == 'all':
                if 'never' in subexpressions or None in subexpressions:
                    result = 'never'                            #there should be no false values!
               
                else:
                    #remove 'always' values as they have no effect
                    subexpressions = [subexpr for subexpr in subexpressions if subexpr != 'always']

                    if len(subexpressions) == 0:
                        result = 'always'                       #no true values means this expression is good
                    elif len(subexpressions) == 1:
                        result = subexpressions[0]              #as there is only 1 operator, the 'all' isn't needed
                    else:
                        result = [opr] + subexpressions 
            
            #optimize the 'none' operator
            elif opr == 'none':
                if 'always' in subexpressions:
                    result = 'never'                            #there should be no true values!

                else:                
                    #remove 'never' values as they have no effect
                    subexpressions = [subexpr for subexpr in subexpressions if subexpr != 'never']

                    if len(subexpressions) == 0:
                        result = 'always'                       #no false values means this expression is good
                    else:
                        result = [opr] + subexpressions         #for anything else we need the operator
            
            else:
                #unknown operator - so let's just return it as is
                result = expression

        cache[c_hash] = result
        
        return result
            
#####################################################################

def internalCompileKERLtoEnglish(expression, model):
    """
        Return an English representation of the specified compiled
        KERL expression for the provided Model.

        PARAM: expression - a non-optimized KERL expression in parsed format (see parseKERL)
        PARAM: model - a Model instance to scope the evaluation of the expression

        RETURNS: "" if there is no expression

        RETURN TYPE: String         
    """
    
    #for no expression we assume ''
    if expression is None:
        return ""
    
    else:
        #get the operator from the expression
        operator = expression[0]

        #-------------------------------------------------------------
        #KERL operator? (any, all, none)
        if operator == "all":
            description = "all of the following conditions are satisfied; "
            binaryOperator = ", "
            lastBinaryOperator = " and "
            prefixOperator = ""
            postfixOperator = ""

        elif operator == "any":
            description = "one or more of the following conditions are satisfied; "
            binaryOperator = ", "
            lastBinaryOperator = " or "
            prefixOperator = ""
            postfixOperator = ""

        elif operator == "none":
            description = "none of the following conditions are satisfied; "
            binaryOperator = ", "
            lastBinaryOperator = " and "
            prefixOperator = ""
            postfixOperator = ""

        elif operator == "currentuser":
            return "(Current User)"


        elif operator == "model":
            
            if expression[1] in KERLOperators.Equal:
                return " is in the model %s" % expression[2]
            else:
                raise InvalidKerlOperator, expression[1]
            

        #-------------------------------------------------------------
        #assessment operator?
        elif operator == "assessment":
            #createdby operator   
            if len(expression) == 3 and expression[1] == 'createdby':         
                user = model.getUserProviderService().getUser(expression[2])
                
                if user:
                    return " they were created by the user '%s'" % str(user.getFullname())
                else:
                    return " they were created by the user '%s'" % str(expression[2])
                
            elif len(expression) == 3 and expression[1] == 'responsible':         
                user = model.getUserProviderService().getUser(expression[2])
                
                if user:
                    return " the user '%s' is reponsible" % str(user.getFullname())
                else:
                    return " the user '%s' is responsible" % str(expression[2])

            #ischeckedout operator   
            elif len(expression) == 2 and expression[1] == 'ischeckedout':
                return " they are checked out (currently in progress)"

            #islocked operator   
            elif len(expression) == 2 and expression[1] == 'islocked':
                return " they are locked"
            elif len(expression) == 2 and expression[1] == 'isnotlocked':
                return " they are not locked (open)"

            elif len(expression) == 3 and expression[1] == 'inrepository':
                try: repoTitle = model.getComponent(expression[2]).getTitleOrId()
                except AttributeError: repoTitle = expression[2]
                
                return " they are in the '%s' repository" % repoTitle
                
            elif len(expression) == 3 and expression[1] == 'notinrepository':
                try: repoTitle = model.getComponent(expression[2]).getTitleOrId()
                except AttributeError: repoTitle = expression[2]
                
                return " they are not in the '%s' repository" % repoTitle

            elif len(expression) == 3 and expression[1] == 'onlyinrepository':                
                try: repoTitle = model.getComponent(expression[2]).getTitleOrId()
                except AttributeError: repoTitle = expression[2]
                
                return " they are only in the '%s' repository" % repoTitle

            elif len(expression) == 2 and expression[1] == 'onlyincurrentuserrepository':         
                #TODO: not supported yet
                return ""

            elif len(expression) == 2 and expression[1] == 'isorphaned':         
                return " they are not in a repository (they are orphans)"

            #date operators
            elif len(expression) == 3 and expression[1] in ['createdbefore','createdafter','modifiedbefore','modifiedafter']:
                if expression[1] == 'createdbefore':
                    return " that were created before %s" % (expression[2],)
                elif expression[1] == 'createdafter':
                    return " that were created after %s" % (expression[2],)
                if expression[1] == 'modifiedbefore':
                    return " that were modified before %s" % (expression[2],)
                elif expression[1] == 'modifiedafter':
                    return " that were modified after %s" % (expression[2],)
                else:
                    #TODO: there is some problem with the expression.  we should throw an exception
                    return ""
                
            else:
                #TODO: there is some problem with the expression.  it's not supported!
                return ""

        #-------------------------------------------------------------
        #a component operator?
        elif expression[0] == 'anycomponent':
            
            return " any component contains %s %s" % ( expression[1], expression[2] )
        
        elif isComponentOperator(expression):

            #get the component 
            componentIdentity = expression[0]
                
            
            if componentIdentity.startswith('idx:'):
                return "(ToDO: Searchindex Query to english)"
            
            #component = model.getComponent(componentIdentity)
                
            # getComponentByIdentity is a skin script that uses the catalog
            # to find a component on any model with the specified identity.
            component = model.getComponentByIdentity( componentIdentity )
                
            if not component:
                raise ComponentNotFound(componentIdentity)

            return component.compileKERLtoEnglish(expression)
        
        #-------------------------------------------------------------
        #process the KERL operator

        #a list of expressions to join
        compiledExpressions = []

        #iterate over each of the sub expressions, converting them to English expression as necessary
        for subexpression in expression[1:]:

            #compile the current sub expression
            #(may return "")
            compiledExpression = internalCompileKERLtoEnglish(subexpression, model)

            if compiledExpression != "":
                compiledExpressions = compiledExpressions + [compiledExpression]

        #now build the result English from the compiled expressions
        #(don't return anything if we have no compiled expressions)
        if len(compiledExpressions) == 0:
            return ""

        #(return the expression itself if there is only one compiled expression)                
        elif len(compiledExpressions) == 1:
            return prefixOperator + compiledExpressions[0] + postfixOperator

        #many sub expressions
        else:
            result = ""
            for compiledExpression in compiledExpressions:
                #always add the binary operator after the first compiled expression
                #except it's the last operator
                if compiledExpression != compiledExpressions[len(compiledExpressions)-1]:
                    if len(result) > 0:
                        result = result + binaryOperator
                else:
                    if len(result) > 0:
                        result = result + lastBinaryOperator

                result = result + prefixOperator + compiledExpression + postfixOperator  

            return "(" + description + result + ")"    
    
#####################################################################

def compileKERLtoEnglish(expression, model):
    """
        Return an English representation of the specified compiled
        KERL expression for the provided Model.
        
        To ensure simplified english expressions are returned, pass
        in an expression that is the result of the simplifyKERL 
        method (in this module)

        PARAM: expression - a non-optimized KERL expression in parsed format (see parseKERL)
        PARAM: model - a Model instance to scope the evaluation of the expression

        RETURNS: "" if there is no expression

        RETURN TYPE: String         
    """
    
    
    #for no expression we assume 'all assessments'
    if expression is None:
        return "All assessments for %s" % model.getTitleOrId()
    
    else:
        englishKERL = internalCompileKERLtoEnglish(expression, model)
        
        #drop any enclosing ('s )'s at the outer level
        if englishKERL and englishKERL[0] == '(':
            englishKERL = englishKERL[1:-1]
            #englishKERL = englishKERL[1:]
            #englishKERL = englishKERL[0:len(englishKERL)-1]
            
        if model:
            modelTitle = model.getTitleOrId()
        else:
            modelTitle = "All models"            
            
        english = "Assessments for %s where %s " % (modelTitle, englishKERL)
        
        # Hack - Whitespace at beginning and end of clauses arn't consistant. As a quick fix, just remove double whitespace
        # (Of course, this won't match triple whitespace, but I'm assuming this won't creep in).
        # - Jamesd.
        english = english.replace("  "," ")
        
        return english
        

#####################################################################

def internalSimplifyKERLAllExpression(expression):
    """
        Attempt to simpify the 'all' KERL expression
    """

    #ensure the operator is 'all'
    operator = expression[0]
    if operator == 'all':

        #RULE: ['all'] -> None
        if len(expression) == 1:
            return None
            
        #RULE: ['all', X] -> simplifyKERL(X)
        #RULE: ['all', None] -> None
        if len(expression) == 2:
            return simplifyKERL(expression[1])
    
        #RULE: ['all', ['all', Xn], Y] -> simplifyKERL(['all', Xn, Y])
        #RULE: ['all', None, Y] -> simplifyKERL(['all', Y])
        anyPromotions = false
        result = ['all']
        for subexpression in expression[1:]:
            if subexpression is None:
                anyPromotions = true
            else:
                operator = subexpression[0]
                if type(operator) is types.ListType and operator[0] == "reference":
                    result = result + [subexpression]
                elif operator == "all":
                    result = result + subexpression[1:]
                    anyPromotions = true
                else:
                    result = result + [subexpression]
        if anyPromotions:
            return simplifyKERL(result)

        #RULE: ['all', Xn] -> ['all', simplifyKERL(Xn)]
        previous = result
        result = ['all']
        for subexpression in previous[1:]:
            result = result + [simplifyKERL(subexpression)]
            
        if result != previous:
            return simplifyKERL(previous)
            
        #RULE: X -> X            
        else:  
            return result
    
    #not the 'all' operator
    else:
        return expression

#####################################################################

def simplifyKERL(expression):
    """
        Simplifies the specified KERL by removing redundant expressions
    """
    
    
    #the None expression can't be simplified
    if expression is None:
        return None

    #attempt to simplify the expression
    else:
        operator = expression[0]

        #the 'reference' operator can't be simplified
        if type(operator) is types.ListType and operator[0] == "reference":
            return expression
        
        #attempt to simplify the 'all' operator
        elif operator == 'all':
            return internalSimplifyKERLAllExpression(expression)
        
        #assume the expression can't be simplified
        else:
            return expression

#####################################################################
